/*
 * Copyright (C) 2012 Igalia, S.L.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "config.h"
#include "GLContextEGL.h"

#include "GraphicsContext3D.h"
#include "OpenGLShims.h"
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <canvas.h>

namespace WebCore {

static EGLDisplay gSharedEGLDisplay = EGL_NO_DISPLAY;
static const EGLenum gGLAPI = EGL_OPENGL_ES_API;

static EGLDisplay sharedEGLDisplay()
{
    static bool initialized = false;
    if (!initialized) {
        initialized = true;
        gSharedEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);

        EGLint major;
        EGLint minor;

        if (gSharedEGLDisplay != EGL_NO_DISPLAY && (!eglInitialize(gSharedEGLDisplay, &major, &minor) || !eglBindAPI(gGLAPI)))
            gSharedEGLDisplay = EGL_NO_DISPLAY;

#if PLATFORM(MANX)
        if (gSharedEGLDisplay != EGL_NO_DISPLAY)
            eglSwapInterval(gSharedEGLDisplay, 0);
#endif
    }
    return gSharedEGLDisplay;
}

static const EGLint gContextAttributes[] = {
#if USE(OPENGL_ES_2)
    EGL_CONTEXT_CLIENT_VERSION, 2,
#endif
    EGL_NONE
};

static bool getEGLConfig(EGLConfig* config, GLContextEGL::EGLSurfaceType surfaceType)
{
    const EGLint attributeListForWindowSurface[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_DEPTH_SIZE, 0,
        EGL_STENCIL_SIZE, 0,
        EGL_CONFIG_ID, 4, // FIXME: Remove this workaround.
        EGL_NONE
    };

    const EGLint attributeListForPbufferSurface[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
        EGL_NONE
    };

    const EGLint* attributeList = 0;
    switch (surfaceType) {
    case GLContextEGL::PbufferSurface:
        attributeList = attributeListForPbufferSurface;
        break;
    case GLContextEGL::WindowSurface:
        attributeList = attributeListForWindowSurface;
        break;
    }

    EGLint numberConfigsReturned;
    return eglChooseConfig(sharedEGLDisplay(), attributeList, config, 1, &numberConfigsReturned) && numberConfigsReturned;
}

GLContext* GLContextEGL::createOffscreenSharingContext()
{
    return createContext(0, this);
}

GLContextEGL* GLContextEGL::createWindowContext(EGLNativeWindowType window, GLContext* sharingContext)
{
    EGLContext eglSharingContext = sharingContext ? static_cast<GLContextEGL*>(sharingContext)->m_context : 0;

    EGLDisplay display = sharedEGLDisplay();
    if (display == EGL_NO_DISPLAY)
        return nullptr;

    EGLConfig config;
    if (!getEGLConfig(&config, WindowSurface))
        return nullptr;

    EGLContext context = eglCreateContext(display, config, eglSharingContext, gContextAttributes);
    if (context == EGL_NO_CONTEXT)
        return nullptr;

    EGLSurface surface = eglCreateWindowSurface(display, config, window, 0);
    if (surface == EGL_NO_SURFACE)
        return nullptr;

    return new GLContextEGL(context, surface, WindowSurface);
}

GLContextEGL* GLContextEGL::createPbufferContext(EGLContext sharingContext)
{
    EGLDisplay display = sharedEGLDisplay();
    if (display == EGL_NO_DISPLAY)
        return nullptr;

    EGLConfig config;
    if (!getEGLConfig(&config, PbufferSurface))
        return nullptr;

    EGLContext context = eglCreateContext(display, config, sharingContext, gContextAttributes);
    if (context == EGL_NO_CONTEXT)
        return nullptr;

    static const int pbufferAttributes[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
    EGLSurface surface = eglCreatePbufferSurface(display, config, pbufferAttributes);
    if (surface == EGL_NO_SURFACE) {
        eglDestroyContext(display, context);
        return nullptr;
    }

    return new GLContextEGL(context, surface, PbufferSurface);
}

GLContextEGL* GLContextEGL::createContext(EGLNativeWindowType window, GLContext* sharingContext)
{
    if (!sharedEGLDisplay())
        return nullptr;

    static bool initialized = false;
    static bool success = true;
    if (!initialized) {
        success = initializeOpenGLShims();
        initialized = true;
    }
    if (!success)
        return nullptr;

    EGLContext eglSharingContext = sharingContext ? static_cast<GLContextEGL*>(sharingContext)->m_context : 0;
    GLContextEGL* context = window ? createWindowContext(window, sharingContext) : nullptr;

    if (window) {
        uint8_t canvasId = ((_EGLNativeWindowType*)window)->uID;
        if (context)
            context->m_canvasId = canvasId;
        else
            sce::Canvas::releaseId(canvasId);
    }

    if (!context)
        context = createPbufferContext(eglSharingContext);
    
    return context;
}

GLContextEGL::GLContextEGL(EGLContext context, EGLSurface surface, EGLSurfaceType type)
    : m_context(context)
    , m_surface(surface)
    , m_type(type)
    , m_canvasId(0xFF)
{
}

GLContextEGL::~GLContextEGL()
{
    EGLDisplay display = sharedEGLDisplay();
    if (m_context) {
        // Ensure the effects of all previously called GL commands are complete.
        // Otherwise eglDestroyContext() may deadlock.
        makeContextCurrent();
        glFinish();

        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        eglDestroyContext(display, m_context);
    }

    if (m_surface)
        eglDestroySurface(display, m_surface);

    if (m_canvasId != 0xFF)
        sce::Canvas::releaseId(m_canvasId);
}

bool GLContextEGL::canRenderToDefaultFramebuffer()
{
    return m_type == WindowSurface;
}

bool GLContextEGL::makeContextCurrent()
{
    ASSERT(m_context && m_surface);

    GLContext::makeContextCurrent();
    if (eglGetCurrentContext() == m_context)
        return true;

    return eglMakeCurrent(sharedEGLDisplay(), m_surface, m_surface, m_context);
}

void GLContextEGL::swapBuffers()
{
    ASSERT(m_surface);
    PUSH_CPU_MARKER("eglSwapBuffers", 0);
    eglSwapBuffers(sharedEGLDisplay(), m_surface);
    POP_CPU_MARKER();
    CPU_SYNC();
}

#if ENABLE(WEBGL)
PlatformGraphicsContext3D GLContextEGL::platformContext()
{
    return m_context;
}
#endif

GLContext* GLContext::getContextForWidget(PlatformWidget widget)
{
    EGLNativeWindowType window = (EGLNativeWindowType)widget;

    return GLContextEGL::createContext(window);
}

} // namespace WebCore
